package com.lsk.netdisk.file.helpers;

import com.lsk.netdisk.common.redis.core.RedisComponent;
import com.lsk.netdisk.common.response.exception.HttpException;
import com.lsk.netdisk.file.authz.Authz;
import com.lsk.netdisk.file.authz.Permission;
import com.lsk.netdisk.file.mappers.DirsMapper;
import com.lsk.netdisk.file.mappers.FilesMapper;
import com.lsk.netdisk.file.mappers.ServersMapper;
import com.lsk.netdisk.file.model.Dir;
import com.lsk.netdisk.file.model.File;
import com.lsk.netdisk.file.model.Server;
import com.lsk.netdisk.file.utils.FileSizeUtil;
import com.lsk.netdisk.file.utils.HashUtil;
import com.lsk.netdisk.file.utils.SpringUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.io.FileSystemResource;
import org.springframework.http.*;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.multipart.MultipartFile;

import java.io.ByteArrayInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@Slf4j
public class FileHelper {
	private static DirsMapper dirsMapper = SpringUtil.getBean(DirsMapper.class);
	private static FilesMapper filesMapper = SpringUtil.getBean(FilesMapper.class);
	private static ServersMapper serversMapper = SpringUtil.getBean(ServersMapper.class);
	private static RedisComponent redis = SpringUtil.getBean(RedisComponent.class);
	private static RestTemplate restTemplate = SpringUtil.getBean(RestTemplate.class);
	private static CloudHelper cloudHelper = SpringUtil.getBean(CloudHelper.class);
	public static Map<String,Object> ls(Integer dirID, Integer uid){
		List<File> files = filesMapper.ls(dirID);
		List<Dir> dirs = dirsMapper.lsDirs(dirID);
		Map<String,Object> result = new HashMap<>();
		result.put("files",files);
		result.put("dirs",dirs);
		Dir thisDir = dirsMapper.queryDirById(dirID);
		result.put("..",thisDir.getParent());
		return result;
	}
	public static void setWorkingDir(Integer dirID,Integer uid){
		redis.set(uid+"-WORKDIR",dirID,0);
	}
	public static void mkdir(String name,Integer uid){
		Integer parentDirID = redis.get(uid+"-WORKDIR",Integer.class);
		Dir parentDir = dirsMapper.queryDirById(parentDirID);
		Dir dir = new Dir();
		dir.setName(name);
		dir.setOwnerID(uid);
		dir.setParent(parentDir.getId());
		dir.setPermission(parentDir.getPermission());
		dirsMapper.addDir(dir);
	}
	public static Integer mkUserDir(String username,Integer uid){
		Dir dir = new Dir();
		dir.setName(username);
		dir.setParent(0);
		dir.setOwnerID(uid);
		dir.setPermission(Permission.Owner.toString());
		dirsMapper.addDir(dir);
		return dirsMapper.queryDirId(dir);
	}
	public static void uploadFile(Integer uid, MultipartFile file) throws Exception{
		if (!Authz.checkSpace(file,uid)){
			throw new HttpException("Space is not enough.",403);
		}
		java.io.File dir = new java.io.File("D://tmp/netdisk");
		java.io.File tmpFile = new java.io.File(dir,Double.toString(Math.random()));
		file.transferTo(tmpFile);
		String fileHash = HashUtil.clacFileHash(tmpFile);
		File localFile = filesMapper.queryFileByHash(fileHash);
		Integer parentDir = getWorkingDirId(uid);
		if (localFile == null) {
			Server server = chooseServer(file);
			String uploadURL = server.getUploadURL();
			String storageName = doUploadFile(tmpFile, uploadURL);
			File fileBean = new File();
			fileBean.setFileName(file.getOriginalFilename());
			fileBean.setHash(fileHash);
			fileBean.setOwnerId(uid);
			fileBean.setStorageName(storageName);
			fileBean.setStorageServer(server.getId());
			fileBean.setParentDir(parentDir);
			filesMapper.addFile(fileBean);
		}else{
			File fileBean = new File();
			fileBean.setStorageServer(localFile.getStorageServer());
			fileBean.setStorageName(localFile.getStorageName());
			fileBean.setOwnerId(uid);
			fileBean.setHash("");
			fileBean.setFileName(file.getOriginalFilename());
			filesMapper.addFile(fileBean);
		}
		tmpFile.delete();
	}
	private static Server chooseServer(MultipartFile file){
		double fileSize = FileSizeUtil.FormatFileSize(file.getSize(),FileSizeUtil.SIZETYPE_GB);
		List<Server> servers = serversMapper.queryAllServers();
		for(Server server : servers){
			try {
				String extraSpaceURL = server.getExtraSpaceURL();
				String extraSpaceString = restTemplate.getForObject(extraSpaceURL, String.class);
				double serverExtraSpace = Double.parseDouble(extraSpaceString);
				if (fileSize > serverExtraSpace){
					continue;
				}else{
					return server;
				}
			}catch (Exception e){
				log.error("Error",e);
				throw new RuntimeException(e);
			}
		}
		throw new RuntimeException("No server has enough server!");
	}
	private static String doUploadFile(java.io.File file, String uploadURL){
		HttpHeaders httpHeaders = new HttpHeaders();
		httpHeaders.setContentType(MediaType.parseMediaType("multipart/form-data"));
		FileSystemResource fileSystemResource = new FileSystemResource(file);
		MultiValueMap<String, Object> multiValueMap = new LinkedMultiValueMap<>();
		multiValueMap.add("file",fileSystemResource);
		multiValueMap.add("filename",file.getName());
		HttpEntity<MultiValueMap<String, Object>> httpEntity = new HttpEntity<>(multiValueMap,httpHeaders);
		return restTemplate.postForObject(uploadURL,httpEntity,String.class);
	}
	public static String getDirName(Integer id){
		return dirsMapper.queryDirById(id).getName();
	}
	public static void cd(Integer dirID, String token){
		Integer uid = cloudHelper.getCurrentUserID(token);
		if (!Authz.checkPermission(dirID,uid,token)){
			throw new HttpException("Permission Dined.",403);
		}
		setWorkingDir(dirID,uid);
	}
	public static Integer getWorkingDirId(Integer uid){
		return redis.get(uid + "-WORKDIR",Integer.class);
	}
	public static java.io.File downloadFile(Integer uid, Integer fileID){
		File file = filesMapper.queryFileById(fileID);
		String storageName = file.getStorageName();
		String name = file.getFileName();
		Integer storageServer = file.getStorageServer();
		Server server = serversMapper.queryServerById(storageServer);
		String downloadUrl = server.getDownloadURL();
		InputStream is = null;
		OutputStream os = null;
		try{
			java.io.File tempFile = new java.io.File("D://tmp/netdisk/"+name);
			MultiValueMap<String,String> params = new LinkedMultiValueMap<>();
			params.add("fname",storageName);
			HttpEntity<byte[]> httpEntity = new HttpEntity<>(new HttpHeaders());
			String url = downloadUrl + "?fname=" + storageName;
			log.info(url);
			ResponseEntity<byte[]> responseEntity = restTemplate.exchange(url,HttpMethod.GET,null,byte[].class);
			byte[] resp = responseEntity.getBody();
			is = new ByteArrayInputStream(resp);
			os = new FileOutputStream(tempFile);
			int len = 0;
			byte[] buf = new byte[1024];
			while ((len = is.read(buf, 0, 1024)) != -1) {
				os.write(buf, 0, len);
			}
			os.flush();
			return tempFile;
		}catch (Exception e){
			log.error("Download Failed",e);
			return null;
		}finally {
			try {
				is.close();
			}catch (Exception e){
				log.error("Cannot close Input stream",e);
			}
			try {
				os.close();
			}catch (Exception e){
				log.error("Cannot close output stream",e);
			}
		}
	}
}
